/*	$Id: client.c 1062 2010-04-23 05:58:05Z phrakt $	*/
/*
 * Copyright (c) 2003 Jean-Francois Brousseau <phrakt@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <sys/socket.h>

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <unistd.h>

#include <canopy/log.h>
#include <canopy/mem.h>
#include <canopy/thread.h>

#include "msg.h"
#include "http.h"
#include "client.h"
#include "private.h"




static int   http_client_connect   (http_client_t *, const char *, int);


/*
 * http_client_alloc()
 *
 * Allocate a new HTTP client structure with <agent> as the User-Agent string
 * and a default HTTP version of <version>.
 *
 * Returns a pointer to the new HTTP client structure on success, or NULL
 * on failure.
 */
http_client_t*
http_client_alloc(const char *agent, enum http_version version)
{
	http_client_t *cli;

	if ((cli = cnp_malloc(sizeof(*cli))) == NULL) {
		http_log_errno("failed to allocate HTTP client");
		return (NULL);
	}
	memset(cli, 0, sizeof(*cli));

	if (cnp_rwlock_init(&cli->cl_rwlock, NULL) == -1) {
		http_log_err("failed to initialize HTTP client lock");
		cnp_free(cli);
		return (NULL);
	}

	return (cli);
}

/*
 * http_client_free()
 *
 */
void
http_client_free(http_client_t *cli)
{

	cnp_free(cli);
}

/*
 * http_client_request()
 *
 * Send the HTTP request <req>
 *
 * Returns a pointer to the returned HTTP response message on success, or
 * NULL on failure.
 */
struct http_msg*
http_client_request(http_client_t *cli, http_msg_t *req)
{
	int port;
	ssize_t len;
	const char *host;
	http_msg_t *resp = NULL;

	if (req->msg_type != HTTP_MSG_REQ) {
		http_log_err("failed to send HTTP request: "
		    "Invalid message type");
		return (NULL);
	}

	port = http_uri_getport(req->msg_req.req_url);
	if ((host = http_uri_gethost(req->msg_req.req_url)) == NULL) {
		http_log_err("no host specified in request URL");
		return (NULL);
	}

	if (http_client_connect(cli, host, port) == -1)
		return (NULL);

	if (cli->cl_user_agent != NULL)
		http_msg_sethdr(req, "User-Agent", cli->cl_user_agent);

	if (http_sock_sendmsg(cli->cl_sock, req, 0) == -1)
		return (NULL);

	if ((resp = http_msg_alloc(HTTP_MSG_RESP)) == NULL)
		return (NULL);

	len = http_sock_recvmsg(cli->cl_sock, resp, 0);

	return (resp);
}

/*
 * http_client_connect()
 *
 * Open a connection to the host <host> for the HTTP client <cli>.  The string
 * <host> can contain a hostname or a presentation-format IPv4 or IPv6 address.
 *
 * Returns 0 on success, or -1 on failure.
 */
static int
http_client_connect(http_client_t *cli, const char *host, int port)
{
	int ret;
	http_sockaddr_t addr;
	struct addrinfo *info, *ip;

	http_log_trace("getaddrinfo(%s, NULL, NULL, %p)", host, &info);
	if ((ret = getaddrinfo(host, NULL, NULL, &info)) != 0) {
		return (-1);
	}

	ret = -1;
	for (ip = info; ip != NULL; ip = ip->ai_next) {
		if (ip->ai_addrlen > sizeof(addr))
			continue;

		memcpy(&addr, ip->ai_addr, ip->ai_addrlen);
		http_addr_setport(&addr, port);

		if ((cli->cl_sock = http_sock_connect(&addr)) != NULL) {
			ret = 0;
			break;
		}
	}

	return (ret);
}
